import {ElMessage} from "element-plus";

export interface GameMode {
    num: number,                // 网格的数量
    bomb: number,               // 地雷的数量
    cell: number,               // 每个网格的大小
    name?: string               // 模式的名称
}

export interface GameOption {
    mode: GameMode;
    width: number;
    height: number;
    offset: number[];
}

export class MineMap {
    private option: GameOption;
    private ctx: CanvasRenderingContext2D;

    constructor(option: GameOption, ctx: CanvasRenderingContext2D) {
        this.option = option;
        this.ctx = ctx;
    }
    draw(mode?: GameMode) {
        if (mode !== undefined) {
            this.option.mode = mode;
        }
        this.ctx.beginPath()
        this.ctx.fillStyle = '#3f72af'
        // 清空画布
        this.ctx.clearRect(
            0,0,
            this.option.width,
            this.option.height
        )
        for (let i = 0; i < this.option.mode.num; i++) {
            for (let j = 0; j < this.option.mode.num; j++) {
                this.ctx.fillRect(
                    this.option.offset[0] + i * this.option.mode.cell + 2 * i,
                    this.option.offset[1] + j * this.option.mode.cell + 2 * j,
                    this.option.mode.cell,
                    this.option.mode.cell
                )
            }
        }
        this.ctx.closePath()
    }
}

export class Point {
    x: number;
    y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

export class MineSweeper {
    private option: GameOption;
    private ctx: CanvasRenderingContext2D;
    private bomb: Point[] = [];
    private bombImg: HTMLImageElement = new Image();
    private flagImg: HTMLImageElement = new Image();
    private readonly map: number[][];
    private readonly flagMap: number[][]; // 标记地图，是否被翻开，0 未翻开，1 已翻开，2 标记
    public gameStatus: boolean = false; // 游戏状态，true 游戏中，false 游戏结束
    private boundClickListener: (() => void) | null = null;
    private boundContextMenuListener: (() => void) | null = null;

    constructor(option: GameOption, ctx: CanvasRenderingContext2D) {
        this.option = option;
        this.ctx = ctx;
        this.map = new Array(this.option.mode.num + 2).fill(0)
            .map(() => new Array(this.option.mode.num + 2).fill(0));
        this.flagMap = new Array(this.option.mode.num + 2).fill(0)
            .map(() => new Array(this.option.mode.num + 2).fill(0));
        this.bombImg.src = '\n'
        this.flagImg.src = 'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB0PSIxNzQ0ODEyODMzNDY4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjUyNDMiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2Ij48cGF0aCBkPSJNODM1Ljk5OSA2My41NDJhMTAuMDEyIDEwLjAxMiAwIDAgMC0xMC4zMjIgMC42Yy0xLjA0MSAwLjcxMy0xMDQuODQ4IDcxLjMzMi0xNzMuNDQgNzEuMzM5LTQuMDIgMC03Ljg3My0wLjI1Mi0xMS40NTMtMC43NS0yNy42ODUtMy44NDUtNDkuNTI4LTEzLjI5LTcyLjY1Ni0yMy4yODctMzMuMDctMTQuMjk3LTY3LjI2Ny0yOS4wODEtMTIwLjk1LTI5LjA4MS00LjAyIDAtOC4xNTQgMC4wODUtMTIuMjg4IDAuMjU1LTg0LjIzMiAzLjQzOC0xNTcuMjUgMjUuNDA2LTE3OC43NiAzMi40NThsLTExLjk5Ny0xMi45MzlhMTAuMDA1IDEwLjAwNSAwIDAgMC0xMS4xNy0yLjQzOGwtNDQuMTMgMTguMzAzYTEwLjAwNSAxMC4wMDUgMCAwIDAtNS44NTcgMTEuNzM2bDIxMi4yMTIgODI0LjM2MWExMC4wMDIgMTAuMDAyIDAgMCAwIDkuNjg5IDcuNTEgMTAuMDA4IDEwLjAwOCAwIDAgMCA3LjAwMi0yLjg1OGw1MS4wMjUtNDkuOTg2YTEwLjAwMiAxMC4wMDIgMCAwIDAgMi43ODUtOS4yM2MtMC40NS0yLjExOC00NS4wOTctMjEyLjMyLTQ3LjgyLTI1OS42MTktMS40NDUtMjUuMSA4LjQ5Ny01MC45MDMgMjcuMjc3LTcwLjc5NyAyMC4yODgtMjEuNDkgNjEuMDEzLTQ3LjEwNyAxMzkuODE4LTQ3LjExIDQ0LjQ5LTAuMDAxIDg1LjUwMy01Ljc4OCAxMjEuODk2LTE3LjIgMjkuMzM5LTkuMiA1NS43NjctMjIuMDU0IDc4LjU1LTM4LjIwOSAzOS4yOS0yNy44NTggNTQuMTg2LTU1LjkyIDU0LjgtNTcuMTAyYTkuOTk4IDkuOTk4IDAgMCAwIDEuMTMyLTQuNjJWNzIuMzkzQTEwLjAxIDEwLjAxIDAgMCAwIDgzNiA2My41NDJ6IiBmaWxsPSIjRkYzMzY2IiBwLWlkPSI1MjQ0Ij48L3BhdGg+PC9zdmc+'
        this.generateBomb();
        this.changeMap()
    }

    private clickListener(e: MouseEvent) {
        if (this.gameStatus === false) {
            removeEventListener('click', this.boundClickListener)
            removeEventListener('contextmenu', this.boundContextMenuListener)
            return
        }
        if (e.button === 0) {
            const x = Math.floor((e.offsetX - this.option.offset[0]) / (this.option.mode.cell + 2));
            const y = Math.floor((e.offsetY - this.option.offset[1]) / (this.option.mode.cell + 2));
            // 越界点击
            if (x < 0 || x >= this.option.mode.num || y < 0 || y >= this.option.mode.num) {
                return
            }
            // 无效点击
            if (this.flagMap[x + 1][y + 1] === 2) {
                return
            }
            if (this.flagMap[x + 1][y + 1] === 0) {
                // 没有被翻开
                this.flagMap[x + 1][y + 1] = 1;
            } else if (this.flagMap[x + 1][y + 1] === 1) {
                // 被翻开
                return
            }
            if (this.logic(new Point(x, y)))
                this.drawFlagMap()
            else {
                this.drawMap()
                ElMessage.error('游戏失败！')
                this.gameStatus = false;
                return;
            }
// 获取flagMap中剩余的0和2的数量
            let count = 0;
            for (let i = 0; i < this.option.mode.num; i++) {
                for (let j = 0; j < this.option.mode.num; j++) {
                    if (this.flagMap[i + 1][j + 1] !== 1) {
                        count += 1;
                    }
                }
            }
// 如果flagMap中剩余的0和2数量等于地雷的数量，就游戏胜利
            if (count === this.option.mode.bomb) {
                ElMessage.success('恭喜你，游戏胜利！')
                this.drawMap()
                this.gameStatus = false;
                return;
            }
        }
    }

    private contextMenuListener(e: MouseEvent) {
        if (this.gameStatus === false) {
            removeEventListener('click', this.boundClickListener)
            removeEventListener('contextmenu', this.boundContextMenuListener)
            return
        }
        e.preventDefault();
        const x = Math.floor((e.layerX - this.option.offset[0]) / (this.option.mode.cell + 2));
        const y = Math.floor((e.layerY - this.option.offset[1]) / (this.option.mode.cell + 2));
        if (this.flagMap[x + 1][y + 1] === 0) {
            this.flagMap[x + 1][y + 1] = 2;
        } else if (this.flagMap[x + 1][y + 1] === 2) {
            this.flagMap[x + 1][y + 1] = 0;
        }
        this.drawFlagMap();
    }

    public start() {
        this.boundClickListener = this.clickListener.bind(this);
        this.boundContextMenuListener = this.contextMenuListener.bind(this);
        addEventListener('contextmenu', this.boundContextMenuListener)
        addEventListener('click', this.boundClickListener)
    }

    // 随机生成地雷的位置，并且在地图上绘制
    private generateBomb() {
        // 随机生成地雷的位置
        while (true) {
            if (this.bomb.length === this.option.mode.bomb) {
                break;
            }
            const x = Math.floor(Math.random() * this.option.mode.num);
            const y = Math.floor(Math.random() * this.option.mode.num);
            if (!this.bomb.some((bomb) => bomb.x === x && bomb.y === y)) {
                this.bomb.push(new Point(x, y));
                this.map[x + 1][y + 1] = -1;
            }
        }
    }

    private drawFlagMap() {
        this.ctx.beginPath()
        this.ctx.clearRect(
            0, 0,
            this.option.width,
            this.option.height
        )
        this.ctx.fillStyle = '#fff'
        this.ctx.font = "20px New Times Roman";
        for (let i = 0; i < this.option.mode.num; i++) {
            for (let j = 0; j < this.option.mode.num; j++) {
                if (this.flagMap[i + 1][j + 1] === 1) {
                    if (this.map[i + 1][j + 1] === -1) {
                        this.ctx.drawImage(
                            this.bombImg,
                            this.option.offset[0] + i * this.option.mode.cell + 2 * i,
                            this.option.offset[1] + j * this.option.mode.cell + 2 * j,
                            this.option.mode.cell,
                            this.option.mode.cell
                        )
                    } else {
                        this.ctx.fillText(
                            this.map[i + 1][j + 1].toString(),
                            this.option.offset[0] + i * this.option.mode.cell + 2 * i + this.option.mode.cell / 2 - 5,
                            this.option.offset[1] + j * this.option.mode.cell + 2 * j + this.option.mode.cell / 2 + 5,
                            this.option.mode.cell
                        )
                    }
                }
                if (this.flagMap[i + 1][j + 1] === 2) {
                    this.ctx.drawImage(
                        this.flagImg,
                        this.option.offset[0] + i * this.option.mode.cell + 2 * i,
                        this.option.offset[1] + j * this.option.mode.cell + 2 * j,
                        this.option.mode.cell,
                        this.option.mode.cell
                    )
                }
            }
        }
    }

    public changeMap() {
        this.ctx.beginPath()
        this.ctx.fillStyle = '#fff'
        this.ctx.font = "20px New Times Roman";
        // 清空画布
        this.ctx.clearRect(
            0, 0,
            this.option.width,
            this.option.height
        )
        for (let i = 0; i < this.option.mode.num; i++) {
            for (let j = 0; j < this.option.mode.num; j++) {
                if (this.map[i + 1][j + 1] === -1) {
                    continue;
                }
                // 查找九宫格
                this.map[i + 1][j + 1] = 0
                for (let k = -1; k <= 1; k++) {
                    for (let l = -1; l <= 1; l++) {
                        if (this.bomb.some((bomb) => bomb.x === i + k && bomb.y === j + l)) {
                            this.map[i + 1][j + 1] += 1;
                        }
                    }
                }
            }
        }
        this.ctx.closePath()
    }

    public drawMap() {
        this.ctx.beginPath()
        this.ctx.fillStyle = '#fff'
        this.ctx.font = "20px New Times Roman";
        this.ctx.clearRect(
            0, 0,
            this.option.width,
            this.option.height
        )
        for (let i = 0; i < this.option.mode.num; i++) {
            for (let j = 0; j < this.option.mode.num; j++) {
                if (this.map[i + 1][j + 1] === -1) {
                    this.ctx.drawImage(
                        this.bombImg,
                        this.option.offset[0] + i * this.option.mode.cell + 2 * i,
                        this.option.offset[1] + j * this.option.mode.cell + 2 * j,
                        this.option.mode.cell,
                        this.option.mode.cell
                    )
                } else {
                    this.ctx.fillText(
                        this.map[i + 1][j + 1].toString(),
                        this.option.offset[0] + i * this.option.mode.cell + 2 * i + this.option.mode.cell / 2 - 5,
                        this.option.offset[1] + j * this.option.mode.cell + 2 * j + this.option.mode.cell / 2 + 5,
                        this.option.mode.cell
                    )
                }
            }
        }
    }

    // 游戏逻辑
    private logic(click: Point): boolean {
        // 点击的位置如果是-1，就游戏失败
        if (this.map[click.x + 1][click.y + 1] === -1) {
            return false;
        }
        let zero: Point[] = [];
        // 点击的位置如何时0，就翻开周围的格子，碰到其他非0的格子就停止翻开
        if (this.map[click.x + 1][click.y + 1] === 0) {
            for (let i = -1; i <= 1; i++) {
                for (let j = -1; j <= 1; j++) {
                    // 剔除自己
                    if (i === 0 && j === 0) {
                        this.flagMap[click.x + 1][click.y + 1] = 1;
                    }
                    if (this.map[click.x + i + 1][click.y + j + 1] === 0) {
                        zero.push(new Point(click.x + i, click.y + j));
                    }
                    if (this.map[click.x + i + 1][click.y + j + 1] >= 0) {
                        this.flagMap[click.x + i + 1][click.y + j + 1] = 1;
                    }
                }
            }
            // 广度优先搜索
            while (true) {
                if (zero.length === 0) {
                    break;
                }
                const point = zero.pop()!;
                for (let i = -1; i <= 1; i++) {
                    for (let j = -1; j <= 1; j++) {
                        // 剔除自己
                        if (i === 0 && j === 0) {
                            continue;
                        }
                        // 超过范围
                        if (point.x + i < 0 || point.x + i >= this.option.mode.num
                            || point.y + j < 0 || point.y + j >= this.option.mode.num) {
                            continue;
                        }
                        if (this.map[point.x + i + 1][point.y + j + 1] === 0
                            && this.flagMap[point.x + i + 1][point.y + j + 1] === 0) {
                            zero.push(new Point(point.x + i, point.y + j));
                        }
                        if (this.map[point.x + i + 1][point.y + j + 1] >= 0
                            && this.flagMap[point.x + i + 1][point.y + j + 1] === 0) {
                            this.flagMap[point.x + i + 1][point.y + j + 1] = 1;
                        }
                    }
                }
            }
        }
        // 点击的位置如果是数字，就翻开
        if (this.map[click.x + 1][click.y + 1] >= 0) {
            this.flagMap[click.x + 1][click.y + 1] = 1;
        }
        return true
    }

    public restart() {
        removeEventListener('click', this.boundClickListener)
        removeEventListener('contextmenu', this.boundContextMenuListener)
        this.flagMap.forEach((row) => {
            row.fill(0);
        })
        this.map.forEach((row) => {
            row.fill(0);
        })
        this.bomb = [];
        this.generateBomb();
        this.changeMap()
        this.drawFlagMap()
        this.gameStatus = true;
        this.start()
    }
}
